/*
 * Copyright 2013 Saswat Bhattacharya
 * This file is part of Touchpad Gestures.
 * 
 * Touchpad Gestures is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.

 * Touchpad Gestures is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License
 * along with Touchpad Gestures.  If not, see <http://www.gnu.org/licenses/>.
 */
package touchpadgestures;

import java.awt.Frame;
import java.awt.event.KeyEvent;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import javax.swing.DefaultListModel;
import static java.awt.event.KeyEvent.*;
import java.util.HashMap;
import java.util.Map;
import java.util.logging.Level;
import java.util.logging.Logger;

/**
 *
 * @author Saswat
 */
public class KeySelector extends javax.swing.JDialog {

    public final static Map<String, Integer> vk2char = new HashMap<>();
    public final static Map<Integer, String> char2vk = new HashMap<>();
    private static char common_keys[] = new char[]{'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z',
        '0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
        '+', '-', '*', '/', '\\', '_', '\'', '"', '.', ',', ';', ':', '!', '@', '#', '$', '%', '^', '&', '`', '|', '~',
        '(', ')', '{', '}', '[', ']', '<', '>'};
    //Other commons: tab, space, enter, f1-12, escape, menu, up, down, left, right, num keys, home, end, page up, page down, delete, back space

    static {
        for (Field f : KeyEvent.class.getFields()) {
            try {
                if (f.getName().startsWith("VK_")) {
                    vk2char.put(f.getName(), f.getInt(null));
                    char2vk.put(f.getInt(null), f.getName());
                }
            } catch (IllegalArgumentException | IllegalAccessException ex) {
                Logger.getLogger(KeySelector.class.getName()).log(Level.SEVERE, null, ex);
            }
        }
    }

    /**
     * Creates new form KeySelector
     *
     */
    public KeySelector(java.awt.Frame parent) {
        super(parent, true);
        initComponents();

        fillCommon();
    }

    /**
     * This method is called from within the constructor to initialize the form.
     * WARNING: Do NOT modify this code. The content of this method is always
     * regenerated by the Form Editor.
     */
    @SuppressWarnings("unchecked")
    // <editor-fold defaultstate="collapsed" desc="Generated Code">//GEN-BEGIN:initComponents
    private void initComponents() {

        winCheckBox = new javax.swing.JCheckBox();
        ctrlCheckBox = new javax.swing.JCheckBox();
        altCheckBox = new javax.swing.JCheckBox();
        shiftCheckBox = new javax.swing.JCheckBox();
        jScrollPane1 = new javax.swing.JScrollPane();
        jList1 = new javax.swing.JList();
        jTextField1 = new javax.swing.JTextField();
        jButton1 = new javax.swing.JButton();
        jButton2 = new javax.swing.JButton();

        setDefaultCloseOperation(javax.swing.WindowConstants.DISPOSE_ON_CLOSE);
        setMinimumSize(new java.awt.Dimension(306, 221));

        winCheckBox.setText("Windows Key");
        winCheckBox.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                winCheckBoxActionPerformed(evt);
            }
        });

        ctrlCheckBox.setText("Ctrl");
        ctrlCheckBox.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                ctrlCheckBoxActionPerformed(evt);
            }
        });

        altCheckBox.setText("Alt");
        altCheckBox.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                altCheckBoxActionPerformed(evt);
            }
        });

        shiftCheckBox.setText("Shift");
        shiftCheckBox.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                shiftCheckBoxActionPerformed(evt);
            }
        });

        jList1.addListSelectionListener(new javax.swing.event.ListSelectionListener() {
            public void valueChanged(javax.swing.event.ListSelectionEvent evt) {
                jList1ValueChanged(evt);
            }
        });
        jScrollPane1.setViewportView(jList1);

        jTextField1.setEditable(false);

        jButton1.setText("Ok");
        jButton1.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                jButton1ActionPerformed(evt);
            }
        });

        jButton2.setText("Cancel");
        jButton2.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                jButton2ActionPerformed(evt);
            }
        });

        javax.swing.GroupLayout layout = new javax.swing.GroupLayout(getContentPane());
        getContentPane().setLayout(layout);
        layout.setHorizontalGroup(
            layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
            .addGroup(layout.createSequentialGroup()
                .addContainerGap()
                .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
                    .addGroup(javax.swing.GroupLayout.Alignment.TRAILING, layout.createSequentialGroup()
                        .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
                            .addGroup(layout.createSequentialGroup()
                                .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
                                    .addComponent(ctrlCheckBox)
                                    .addComponent(altCheckBox)
                                    .addComponent(shiftCheckBox))
                                .addGap(44, 44, 44))
                            .addGroup(javax.swing.GroupLayout.Alignment.TRAILING, layout.createSequentialGroup()
                                .addComponent(winCheckBox)
                                .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)))
                        .addComponent(jScrollPane1, javax.swing.GroupLayout.PREFERRED_SIZE, 0, Short.MAX_VALUE))
                    .addGroup(layout.createSequentialGroup()
                        .addComponent(jTextField1, javax.swing.GroupLayout.DEFAULT_SIZE, 196, Short.MAX_VALUE)
                        .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
                        .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
                            .addComponent(jButton2, javax.swing.GroupLayout.PREFERRED_SIZE, 84, javax.swing.GroupLayout.PREFERRED_SIZE)
                            .addComponent(jButton1, javax.swing.GroupLayout.PREFERRED_SIZE, 84, javax.swing.GroupLayout.PREFERRED_SIZE))))
                .addContainerGap())
        );
        layout.setVerticalGroup(
            layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
            .addGroup(javax.swing.GroupLayout.Alignment.TRAILING, layout.createSequentialGroup()
                .addContainerGap()
                .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
                    .addGroup(layout.createSequentialGroup()
                        .addComponent(winCheckBox)
                        .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
                        .addComponent(ctrlCheckBox)
                        .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
                        .addComponent(altCheckBox)
                        .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
                        .addComponent(shiftCheckBox)
                        .addGap(0, 0, Short.MAX_VALUE))
                    .addComponent(jScrollPane1, javax.swing.GroupLayout.DEFAULT_SIZE, 179, Short.MAX_VALUE))
                .addGap(17, 17, 17)
                .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE)
                    .addComponent(jTextField1, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
                    .addComponent(jButton1))
                .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
                .addComponent(jButton2)
                .addContainerGap())
        );

        pack();
    }// </editor-fold>//GEN-END:initComponents

    private void jButton1ActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_jButton1ActionPerformed
        setVisible(false);
    }//GEN-LAST:event_jButton1ActionPerformed

    private void jButton2ActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_jButton2ActionPerformed
        canceled = true;
        setVisible(false);
    }//GEN-LAST:event_jButton2ActionPerformed
    /*
     * if (jToggleButton1.isSelected()) {
            fillAll();
            ctrlCheckBox.setEnabled(false);
            altCheckBox.setEnabled(false);
            shiftCheckBox.setEnabled(false);
            winCheckBox.setEnabled(false);
        } else {
            fillCommon();
            ctrlCheckBox.setEnabled(true);
            altCheckBox.setEnabled(true);
            shiftCheckBox.setEnabled(true);
            winCheckBox.setEnabled(true);
        }
        refreshText();
     */
    private void winCheckBoxActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_winCheckBoxActionPerformed
        refreshText();
    }//GEN-LAST:event_winCheckBoxActionPerformed

    private void ctrlCheckBoxActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_ctrlCheckBoxActionPerformed
        refreshText();
    }//GEN-LAST:event_ctrlCheckBoxActionPerformed

    private void altCheckBoxActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_altCheckBoxActionPerformed
        refreshText();
    }//GEN-LAST:event_altCheckBoxActionPerformed

    private void shiftCheckBoxActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_shiftCheckBoxActionPerformed
        refreshText();
    }//GEN-LAST:event_shiftCheckBoxActionPerformed

    private void jList1ValueChanged(javax.swing.event.ListSelectionEvent evt) {//GEN-FIRST:event_jList1ValueChanged
        System.out.println(Arrays.toString(jList1.getSelectedIndices()));
        refreshText();
    }//GEN-LAST:event_jList1ValueChanged

    public static String intercaps(String s) {
        s = s.toLowerCase();
        if (s.isEmpty()) {
            return s;
        }

        s = s.substring(0, 1).toUpperCase() + s.substring(1);

        Matcher m = Pattern.compile("( )").matcher(s);

        while (m.find()) {
            int e = m.end();
            if (e + 1 < s.length()) {
                s = s.substring(0, e) + s.substring(e, e + 1).toUpperCase() + s.substring(e + 1);
            }
        }

        return s;
    }

    private void fillCommon() {
        DefaultListModel<String> dlm = new DefaultListModel<>();
        for (char c : common_keys) {
            dlm.addElement(c + "");
        }
        dlm.addElement("Tab");
        dlm.addElement("Space");
        dlm.addElement("Enter");
        dlm.addElement("F1");
        dlm.addElement("F2");
        dlm.addElement("F3");
        dlm.addElement("F4");
        dlm.addElement("F5");
        dlm.addElement("F6");
        dlm.addElement("F7");
        dlm.addElement("F8");
        dlm.addElement("F9");
        dlm.addElement("F10");
        dlm.addElement("F11");
        dlm.addElement("F12");
        dlm.addElement("Escape");
        dlm.addElement("Menu");//VK_CONTEXT_MENU;
        dlm.addElement("Up");
        dlm.addElement("Down");
        dlm.addElement("Left");
        dlm.addElement("Right");
        dlm.addElement("NumPad 0");
        dlm.addElement("NumPad 1");
        dlm.addElement("NumPad 2");
        dlm.addElement("NumPad 3");
        dlm.addElement("NumPad 4");
        dlm.addElement("NumPad 5");//VK_NUMPAD5
        dlm.addElement("NumPad 6");
        dlm.addElement("NumPad 7");
        dlm.addElement("NumPad 8");
        dlm.addElement("NumPad 9");
        dlm.addElement("Home");
        dlm.addElement("End");
        dlm.addElement("Page Up");//VK_PAGE_UP
        dlm.addElement("Page Down");//VK_PAGE_DOWN
        dlm.addElement("Delete");
        dlm.addElement("Backspace");//VK_BACK_SPACE
        jList1.setModel(dlm);
    }

    private void fillAll() {
        DefaultListModel<String> dlm = new DefaultListModel<>();
        for (String string : vk2char.keySet()) {
            dlm.addElement(intercaps(string.substring(3)));
        }
        jList1.setModel(dlm);
    }

    private void refreshText() {
        jTextField1.setText(keysToText());
    }

    private String keysToText() {
        String text = "";
        //if (!jToggleButton1.isSelected()) {
            if (winCheckBox.isSelected()) {
                text += "Win + ";
            }
            if (ctrlCheckBox.isSelected()) {
                text += "Ctrl + ";
            }
            if (altCheckBox.isSelected()) {
                text += "Alt + ";
            }
            if (shiftCheckBox.isSelected()) {
                text += "Shift + ";
            }
            for (int i : jList1.getSelectedIndices()) {
                text += jList1.getModel().getElementAt(i) + ", ";
            }

            if (text.endsWith("+ ")) {
                text = text.substring(0, text.length() - 3);
            }
            if (text.endsWith(", ")) {
                text = text.substring(0, text.length() - 2);
            }
        //}


        return text;
    }

    public boolean isCanceled() {
        return canceled;
    }

    public static Integer[] getKeys(Frame parent) {
        KeySelector ks = new KeySelector(parent);

        ks.setVisible(true);
        if (ks.canceled) {
            return null;
        }
        ArrayList<Integer> keys = new ArrayList<>();
        //if (!ks.jToggleButton1.isSelected()) {
            if (ks.winCheckBox.isSelected()) {
                keys.add(91);//VK_WINDOWS == 504, VK_LWIN == 91...
            }
            if (ks.ctrlCheckBox.isSelected()) {
                keys.add(VK_CONTROL);
            }
            if (ks.altCheckBox.isSelected()) {
                keys.add(VK_ALT);
            }
            if (ks.shiftCheckBox.isSelected()) {
                keys.add(VK_SHIFT);
            }

            for (int i : ks.jList1.getSelectedIndices()) {
                String elm = (String) ks.jList1.getModel().getElementAt(i);
                elm = elm.toUpperCase();
                if (elm.length() == 1) {
                    keys.add((int) elm.charAt(0));
                    continue;
                }
                if (elm.startsWith("NUMPAD ")) {
                    elm = elm.replace(" ", "");
                } else if (elm.equals("BACKSPACE")) {
                    elm = "BACK_SPACE";
                } else if (elm.startsWith("PAGE ")) {
                    elm = elm.replace(" ", "_");
                }
                keys.add(vk2char.get("VK_"+elm));
            }

        /*} else {
            for (int i : ks.jList1.getSelectedIndices()) {
                keys.add(vk2char.get("VK_"+(String)ks.jList1.getModel().getElementAt(i)));
            }
        }*/
        return keys.toArray(new Integer[0]);
    }

    /**
     * @param args the command line arguments
     */
    public static void main(String args[]) {
        /* Set the Nimbus look and feel */
        //<editor-fold defaultstate="collapsed" desc=" Look and feel setting code (optional) ">
        /* If Nimbus (introduced in Java SE 6) is not available, stay with the default look and feel.
         * For details see http://download.oracle.com/javase/tutorial/uiswing/lookandfeel/plaf.html 
         */
        try {
            for (javax.swing.UIManager.LookAndFeelInfo info : javax.swing.UIManager.getInstalledLookAndFeels()) {
                if ("Nimbus".equals(info.getName())) {
                    javax.swing.UIManager.setLookAndFeel(info.getClassName());
                    break;
                }
            }
        } catch (ClassNotFoundException | InstantiationException | IllegalAccessException | javax.swing.UnsupportedLookAndFeelException ex) {
            java.util.logging.Logger.getLogger(KeySelector.class.getName()).log(java.util.logging.Level.SEVERE, null, ex);
        }
        //</editor-fold>

        /* Create and display the dialog */
        java.awt.EventQueue.invokeLater(new Runnable() {
            @Override
            public void run() {
                KeySelector dialog = new KeySelector(new javax.swing.JFrame());
                dialog.addWindowListener(new java.awt.event.WindowAdapter() {
                    @Override
                    public void windowClosing(java.awt.event.WindowEvent e) {
                        System.exit(0);
                    }
                });
                dialog.setVisible(true);
            }
        });
    }
    // Variables declaration - do not modify//GEN-BEGIN:variables
    private javax.swing.JCheckBox altCheckBox;
    private javax.swing.JCheckBox ctrlCheckBox;
    private javax.swing.JButton jButton1;
    private javax.swing.JButton jButton2;
    private javax.swing.JList jList1;
    private javax.swing.JScrollPane jScrollPane1;
    private javax.swing.JTextField jTextField1;
    private javax.swing.JCheckBox shiftCheckBox;
    private javax.swing.JCheckBox winCheckBox;
    // End of variables declaration//GEN-END:variables
    private boolean canceled = false;
    //private ArrayList<Integer> keys = new ArrayList<>();
}
